package com.ec50n9.server.module.role

import com.ec50n9.server.common.ApiException
import com.ec50n9.server.common.mayBeAlreadyExist
import com.ec50n9.server.common.mayBeNotFound
import com.ec50n9.server.module.common.AppCode
import com.ec50n9.server.module.common.renderTo
import com.ec50n9.server.module.resource.ResourceQueryRendered
import com.ec50n9.server.module.resource.ResourceRepository
import com.ec50n9.server.module.user.UserQueryRendered
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Service
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import java.util.*

interface RoleRepository: CrudRepository<Role, String> {

    fun findByCode(code: String): Optional<Role>

    fun findByCodeIn(codes: Collection<String>): List<Role>
}

@Service
class RoleService(private val roleRepository: RoleRepository){
    private val name = "角色"

    fun findOne(code: String): Role = mayBeNotFound(name){
        roleRepository.findByCode(code).orElseThrow()
    }

    fun findAll(): Iterable<Role> = roleRepository.findAll()

    fun newOne(role: Role) = mayBeAlreadyExist(name){
        roleRepository.save(role)
    }

    fun update(role: Role) = roleRepository.save(role)

    fun delete(role: Role) = roleRepository.delete(role)
}

@Tag(name = "角色管理")
@RestController
@RequestMapping("/api/roles")
class RoleController(
    private val roleService: RoleService,
    private val resourceRepository: ResourceRepository
) {

    @Operation(summary = "获取全部", description = "获取全部角色")
    @GetMapping
    fun findAll(): List<RoleQueryRendered> =
        roleService.findAll().renderTo(RoleQueryRendered::class.java)

    @Operation(summary = "通过code获取")
    @GetMapping("/{code}")
    fun findOneByCode(@PathVariable code: String): RoleQueryRendered =
        roleService.findOne(code).renderTo(RoleQueryRendered::class.java)

    @Operation(summary = "新建")
    @PostMapping
    fun newRoles(@RequestBody @Validated createForm: RoleCreateForm): RoleQueryRendered =
        roleService.newOne(createForm.renderTo()).renderTo(RoleQueryRendered::class.java)

    @Operation(summary = "更新")
    @PatchMapping("/{code}")
    fun updateRole(@RequestBody updateForm: RoleUpdateForm, @PathVariable code: String): RoleQueryRendered {
        val role = roleService.findOne(code)
        updateForm.renderTo(role)
        return roleService.update(role).renderTo(RoleQueryRendered::class.java)
    }

    @Operation(summary = "删除")
    @DeleteMapping("/{code}")
    fun deleteRole(@PathVariable code: String) = with(roleService) {
        delete(findOne(code))
    }

    /**
     * 相关接口⬇️
     */

    @Operation(summary = "获取用户列表")
    @GetMapping("/{code}/users")
    fun getUsers(@PathVariable code: String): List<UserQueryRendered> =
        roleService.findOne(code).users.renderTo(UserQueryRendered::class.java)

    @Operation(summary = "获取资源列表")
    @GetMapping("/{code}/resources")
    fun getResources(@PathVariable code: String): List<ResourceQueryRendered> =
        roleService.findOne(code).resources.renderTo(ResourceQueryRendered::class.java)

    @Operation(summary = "替换资源列表")
    @PostMapping("/{code}/resources")
    fun replaceResources(@RequestBody resourceCodeList: List<String>, @PathVariable code: String) {
        val role = roleService.findOne(code)
        role.resources.clear()
        role.resources.addAll(resourceRepository.findByCodeIn(resourceCodeList))
        roleService.update(role)
    }

    @Operation(summary = "绑定资源")
    @PutMapping("/{code}/resources/{resourceCode}")
    fun addResource(@PathVariable code: String, @PathVariable resourceCode: String) {
        val role = roleService.findOne(code)
        val resource = resourceRepository.findByCode(resourceCode)
            .orElseThrow { ApiException(AppCode.OBJECT_NOT_FOUND, "资源不存在") }
        role.resources.add(resource)
        roleService.update(role)
    }

    @Operation(summary = "解绑资源")
    @DeleteMapping("/{code}/resources/{resourceCode}")
    fun removeRole(@PathVariable code: String, @PathVariable resourceCode: String) {
        val role = roleService.findOne(code)
        role.resources.removeIf { resource -> resource.code == resourceCode }
        roleService.update(role)
    }
}